4. 호밍 기능 추가

1. 기본 아이디어

`TickComponent()`에서 아래와 같은 단계로 연산을 한다. 1. 투사체의 Forward 벡터와 투사체에서 타겟까지의 벡터 사이의 각을 구한다. 2. 초당 최대 회전각 변수 `mHomingTurnLimit`와 `DeltaSecond`값을 이용해 실제로 회전할 각을 구한다. 3. 회전 쿼터니안을 구한다. 4. Actor의 Rotation에 회전 적용
void WProjectileMovementComponent::TickComponent(float DeltaTime)
{
    mLifeTimeElapsed += DeltaTime;
    auto Owner = GetOwner().lock();
    if (!Owner || (mLifeSpan > 0 && mLifeTimeElapsed > mLifeSpan))
    {
        if (Owner) Owner->Destroy();
        return;
    }

    if (mbHomingProjectile)
    {
        if (TSharedPtr<WSceneComponent> Target = mHomingTarget.lock())
        {
            XMFLOAT3 CurrentLocation = Owner->GetActorLocation();
            XMVECTOR CurrLocV = XMLoadFloat3(&CurrentLocation);
            XMFLOAT3 TargetLocation = Target->GetWorldLocation();
            XMVECTOR TargetLocV = XMLoadFloat3(&TargetLocation);
            XMVECTOR ToTargetV = XMVectorSubtract(TargetLocV, CurrLocV);

            float DistanceSq = XMVectorGetX(XMVector3LengthSq(ToTargetV));
            if (DistanceSq > 0.00001f) // 거리 체크
            {
                XMFLOAT3 Forward = Owner->GetFowardVector();
                XMVECTOR ForwardV = XMLoadFloat3(&Forward);
                XMVECTOR ToTargetUnitV = XMVector3Normalize(ToTargetV);

                // 1. 두 벡터 사이의 각도 계산
                float Radian = XMVectorGetX(XMVector3AngleBetweenNormals(ForwardV, ToTargetUnitV));
                if (Radian > 0.001f) // 각도 차이가 있을 때만 회전
                {
                    // 2. 턴 리밋 적용 (mHomingTurnLimit가 도 단위라고 가정 시)
                    float MaxStep = XMConvertToRadians(mHomingTurnLimit);

                    // 핵심: Slerp처럼 비율 계산
                    float Alpha = (mHomingTurnLimit <= 0) ? 1.0f : min(1.0f, MaxStep / Radian * DeltaTime);
                    Radian *= Alpha;

                    // 3. 축 계산 및 쿼터니언 생성
                    XMVECTOR AxisV = XMVector3Normalize(XMVector3Cross(ForwardV, ToTargetUnitV));
                    XMVECTOR RotationQuatV = XMQuaternionRotationAxis(AxisV, Radian);

                    // 4. 새로운 방향 벡터 계산
                    XMVECTOR NewForwardV = XMVector3Rotate(ForwardV, RotationQuatV);
                    XMFLOAT3 Up = Owner->GetUpVector();
                    XMVECTOR NewUpV = XMVector3Rotate(XMLoadFloat3(&Up), RotationQuatV);
                    XMFLOAT3 Right = Owner->GetRightVector();
                    XMVECTOR NewRightV = XMVector3Rotate(XMLoadFloat3(&Right), RotationQuatV);
                    
                    XMFLOAT3 NewRotation = FDXMath::GetEulerRotationFromVectors(NewForwardV, NewRightV, NewUpV);
                    Owner->SetActorRotation(NewRotation);
                }
            }
        }
    }

    Super::TickComponent(DeltaTime);
}

2. 문제 해결


2.1. 너무 작은 DeltaSecond


다른 방법으로 해결했으니 아래 참조
1. 호밍 DeltaTime 정밀도 문제 해결
1. Projects/펄어비스 인턴/4주차/__Attachments/TickRate 제한 전.gif
문제 상황: 제대로 호밍이 적용되지 않을 때가 있음.
원인 파악: 100%로 안되는 것이 아니라, 될 때 안될때가 나뉘기 때문에 해당 문제 상황에서 변하는 값은 DeltaSecond 밖에 없음. 따라서 DeltaSecond의 값이 문제가 될 것이라 생각함.
실제로 Tick Rate가 너무 높을 경우 즉, DeltaSecond가 너무 낮을 경우(대략 0.04ms이하), 회전각을 계산할 때 정밀도 문제가 발생함.

float MaxStep = XMConvertToRadians(mHomingTurnLimit);


float Alpha = (mHomingTurnLimit <= 0) ? 1.0f : min(1.0f, MaxStep / Radian * DeltaTime);
Radian *= Alpha;

라디안을 계산하는 코드인데, 실제로 문제 상황에서 값을 대입해 보면

해결책으로, TickRate를 제한
1. Projects/펄어비스 인턴/4주차/__Attachments/TickRate 제한 후.gif

2.2. Forward와 목표 방향 벡터의 각이 180도 일때 예외상황


위의 상황과 같이, 투사체의 Forward와 Target까지 방향 벡터가 180을 이루게 될 경우, 회전축을 만들어내는 코드에서 ZeroVector가 나오게 된다. ```cpp XMVECTOR AxisV = XMVector3Normalize(XMVector3Cross(ForwardV, ToTargetUnitV)); ```

그리고 이 벡터가 assert에서 걸리게 된다.
1. Projects/펄어비스 인턴/4주차/__Attachments/Pasted image 20260125224842.png

따라서 이 예외가 발생할 경우, Forward 대신 Right 벡터로 축을 생성하게 코드를 추가한다.

...

XMVECTOR AxisV = XMVector3Normalize(XMVector3Cross(ForwardV, ToTargetUnitV));
if (XMVector3Equal(AxisV, XMVectorZero()))
{
	XMFLOAT3 Right = Owner->GetRightVector();
	XMVECTOR RightV = XMLoadFloat3(&Right);
	AxisV = XMVector3Normalize(XMVector3Cross(RightV, ToTargetUnitV));
}
XMVECTOR RotationQuatV = XMQuaternionRotationAxis(AxisV, Radian * Alpha);

...